/* $Header: /cvsroot/itrailers/iTrailers/Version\0402/iTunes\040Music\040Store\040Client/HttpReader.cs,v 1.1 2005/12/07 19:53:50 bradsjm Exp $
 * Syndirella: asynchronous HTTP downloader
 * (C) 2002-03 Dmitry Jemerov <yole@yole.ru>
 * Released under the GNU General Public License; see the file COPYING for full license text
 */

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Net;
#if GZIP
using ICSharpCode.SharpZipLib.GZip ;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams ;
#endif

namespace ITMSClient
{
    public class HTTPErrorEventArgs: EventArgs
    {
        public HttpStatusCode ErrorCode;
        public string ErrorMessage;
        public HTTPErrorEventArgs(HttpStatusCode aErrorCode, string aErrorMessage)
        {
            ErrorCode = aErrorCode;
            ErrorMessage = aErrorMessage;
        }
    }

    public class HTTPProgressEventArgs: EventArgs
    {
        private long fCurrentSize;
        private long fTotalSize;

        public long CurrentSize { get { return fCurrentSize; } }
        public long TotalSize   { get { return fTotalSize; } }

        public HTTPProgressEventArgs(long aCurSize, long aTotalSize)
        {
            fCurrentSize = aCurSize;
            fTotalSize = aTotalSize;
        }
    }
    
    public delegate void HTTPErrorEventHandler(object sender, HTTPErrorEventArgs e);
    public delegate void HTTPProgressEventHandler(object sender, HTTPProgressEventArgs e);
    
    public class HTTPReader
	{
		const int BUFFER_SIZE = 1024;
		
		private WebRequest fRequest;
		private Stream fResponseStream = null;
		private byte[] fReadBuffer;
		private MemoryStream fResponseBuffer;
        private string fIfNoneMatch = null;
        private long fContentLength = -1;
        private string fContentEncoding = "";
        private string fContentType = "";
        private string fETag = "";
        private DateTime fLastModified;
        private ICredentials fCredentials = null;

		public event EventHandler GetDone;
        public event EventHandler GetNotModified;
        public event HTTPErrorEventHandler HTTPError;
        public event HTTPProgressEventHandler HTTPProgress;

        private static string fReferer = "";
        private static string fProxyUser = "";
        private static string fProxyPassword = "";

        public static string Referer
        {
            get { return fReferer; }
            set { fReferer = value; }
        }

        public static string ProxyUser
        {
            get { return fProxyUser; }
            set { fProxyUser = value; }
        }

        public static string ProxyPassword
        {
            get { return fProxyPassword; }
            set { fProxyPassword = value; }
        }

        public static WebRequest CreateRequest(Uri uri)
        {
            WebRequest req = WebRequest.Create(uri);
			if(req is HttpWebRequest)
			{
				HttpWebRequest httpReq = req as HttpWebRequest;
				httpReq.Proxy = GetProxy();
				httpReq.UserAgent = "Syndirella/0.92";
				httpReq.Referer = fReferer;
				// enable compression support
				#if GZIP
				httpReq.Headers["Accept-Encoding"] = "gzip" ;
				#endif
			}
            return req;
        }

        public static IWebProxy GetProxy()
        {
            IWebProxy myWebProxy = WebRequest.DefaultWebProxy;
            if (fProxyUser != null && fProxyUser.Length > 0)
                myWebProxy.Credentials = new NetworkCredential(fProxyUser, fProxyPassword);
            else
                myWebProxy.Credentials = System.Net.CredentialCache.DefaultCredentials;
            return myWebProxy;
        }

        public static string FormatProgress(HTTPProgressEventArgs e)
        {
            string s = e.CurrentSize / 1024 + "K";
            if (e.TotalSize > 0)
                s += " of " + e.TotalSize / 1024 + "K";
            return s;
        }

        public static bool IsFatalError(HttpStatusCode errorCode)
        {
            return 
                (errorCode == HttpStatusCode.NotFound) ||
                (errorCode == HttpStatusCode.Forbidden) ||
                (errorCode == HttpStatusCode.Gone);
        }

        public HTTPReader()
        {
			fReadBuffer = new byte[BUFFER_SIZE];
			fResponseBuffer = null;
		}

		public Stream Response
		{
			get { return fResponseBuffer; }
		}

        public string IfNoneMatch
        {
            set { fIfNoneMatch = value; }
        }

        public string ETag
        {
            get { return fETag; }
        }

        public string ContentType
        {
            get { return fContentType; }
        }

        public string ContentTypeCharset
        {
            get
            {
                int pos = fContentType.IndexOf("charset=");
                if (pos >= 0)
                    return fContentType.Substring(pos+8).Trim();
                return "";
            }
        }
        
        public string ContentEncoding
        {
            get { return fContentEncoding; }
        }

        public ICredentials Credentials
        {
            get { return fCredentials; }
            set { fCredentials = value; }
        }

        public void SetLoginCredentials(string userName, string password)
        {
            fCredentials = new NetworkCredential(userName, password);
        }

		public System.Uri getRequestUri()
		{
			return fRequest == null ? null : fRequest.RequestUri;
		}

        public void Get(Uri uri)
        {
            Debug.WriteLine("Starting GET of " + uri.ToString());
            fResponseBuffer = new MemoryStream();
            fRequest = CreateRequest(uri);
            if (fCredentials != null)
                fRequest.Credentials = fCredentials;
            if (fIfNoneMatch != null)
                fRequest.Headers ["If-None-Match"] = fIfNoneMatch;
            fRequest.BeginGetResponse(new AsyncCallback(RespCallback), this);
        }

		private static void RespCallback(IAsyncResult ar)
		{
			HTTPReader reader = (HTTPReader) ar.AsyncState;
			reader.ProcessRespCallback(ar);
		}

		private void ProcessRespCallback(IAsyncResult ar)
		{
			WebResponse response = null;
			HttpWebResponse httpResponse = null ;
            try
            {
                response = fRequest.EndGetResponse(ar);
                Debug.WriteLine("ProcessRespCallback: Successfully received HTTP response for " + fRequest.RequestUri.ToString());
            }
            catch(WebException we)
            {
                response = we.Response;
				if (response is HttpWebResponse)
					httpResponse = response as HttpWebResponse ;
                Debug.WriteLine("ProcessRespCallback: WebException for " + 
                    fRequest.RequestUri.ToString() + ": " + we.Status.ToString() + " " + we.Message);
                if (httpResponse != null && httpResponse.StatusCode == HttpStatusCode.NotModified)
                {
                    OnGetNotModified();
                    fResponseStream = null;
                }
                else if (httpResponse != null)
                    OnHTTPError(httpResponse.StatusCode, we.Message);
                else
                    OnHTTPError(0, we.Message);
                return;
            }
            catch(Exception e)
            {
                Debug.WriteLine("Exception in ProcessRespCallback for " + 
                    fRequest.RequestUri.ToString() + ": " + e.ToString());
                OnHTTPError(0, e.Message);
                return;
            }
            if(response is HttpWebResponse)
				httpResponse = response as HttpWebResponse ;
			fETag = (string) response.Headers ["ETag"];
            if (fETag == null) 
                fETag = "";
            
            // Some servers send an invalid date in the Last-Modified header:
            // http://earful.bitako.com/index.php?a=rdf
			if(httpResponse != null)
			{
				try
				{
					fLastModified = httpResponse.LastModified;
				}
				catch(Exception)
				{
					fLastModified = DateTime.Now;
				}
				fContentEncoding = httpResponse.ContentEncoding;
			}
			else
			{
				fLastModified = DateTime.Now ;
				fContentEncoding = "" ;
			}

            fContentType = response.ContentType;

            fContentLength = response.ContentLength;
            OnHTTPProgress(0, fContentLength);

			fResponseStream = response.GetResponseStream();
			#if GZIP
			string contentEnc = response.Headers["Content-Encoding"];
			if ( contentEnc != null ) 
			{
				contentEnc = contentEnc.ToLower() ;
				if ( contentEnc == "gzip" ) 
				{
					fResponseStream = new GZipInputStream(fResponseStream) ;
				}
				else if ( contentEnc == "deflate" ) 
				{
					fResponseStream = new InflaterInputStream(fResponseStream) ;
				}
			}
			#endif
			fResponseStream.BeginRead(fReadBuffer, 0, BUFFER_SIZE, 
				new AsyncCallback(ReadCallback), this);
		}

		private static void ReadCallback(IAsyncResult ar)
		{
			HTTPReader reader = (HTTPReader) ar.AsyncState;
			reader.ProcessReadCallback(ar);
		}

		private void ProcessReadCallback(IAsyncResult ar)
		{
			int read = -1;
            try
            {
                read = fResponseStream.EndRead(ar);
            }
            catch(Exception e)
            {
                OnHTTPError(0, e.Message);
                fResponseStream.Close();
                return;
            }
			if (read > 0)
			{
                Debug.WriteLine(String.Format("ProcessReadCallback: {0} bytes for {1}",
                    read, fRequest.RequestUri.ToString()));
				fResponseBuffer.Write(fReadBuffer, 0, read);
                OnHTTPProgress(fResponseBuffer.Length, fContentLength);
				fResponseStream.BeginRead(fReadBuffer, 0, BUFFER_SIZE, 
					new AsyncCallback(ReadCallback), this);
			}
			else
			{
                Debug.WriteLine("ProcessReadCallback: done reading for " + fRequest.RequestUri.ToString());
				fResponseStream.Close();
                fResponseBuffer.Seek(0, SeekOrigin.Begin);
				OnGetDone(new EventArgs());
			}
		}

		protected void OnGetDone(EventArgs e)
		{
            if (GetDone != null)
				GetDone(this, e);
		}

        protected void OnGetNotModified()
        {
            if (GetNotModified != null)
                GetNotModified(this, EventArgs.Empty);
        }

        protected void OnHTTPError(HttpStatusCode errorCode, string errorMessage)
        {
            if (HTTPError != null)
                HTTPError(this, new HTTPErrorEventArgs(errorCode, errorMessage));
        }

        protected void OnHTTPProgress(long curSize, long totalSize)
        {
            if (HTTPProgress != null)
                HTTPProgress(this, new HTTPProgressEventArgs(curSize, totalSize));
        }
	}
}
